import * as c from 'colors'
// import * as dotenv from 'dotenv'
import { createServer } from 'http'
import * as jwt from 'jsonwebtoken'
import * as Koa from 'koa'
import * as bodyParser from 'koa-bodyparser'
import * as cors from 'koa-cors'
// 这个包通过设置 http 头来使应用程序更加安全，
// Content-Security-policy: 内容安全策略
// X-DNS-Prefetch-Control: 控制浏览器的DNS 预读取功能， 主动去解析域名解析的功能
// Expect-CT:
import * as helmet from 'koa-helmet'
import * as koaStatic from 'koa-static'

import { GRPCService } from '@services/rpc/GRPCService'
import { join } from 'path'
import 'reflect-metadata'
import * as winston from 'winston'
import { ResponseUtil } from './helpers/ResponseUtil'
import { logger } from './logger'
import { router } from './routes'
import { adminConfig, config, env, whiteHostSet } from './scretConfig'
import { MysqlService } from './services/mysql/MysqlService'
import { RedisService } from './services/redis/RedisServices'
import { WxApiService } from './services/WxApiService'

// 从 .env 文件中加载环境变量， 其中配置了 api 秘钥和密码
// Load environment variables from .env file, where API keys and passwords are configured
// const path = resolve(__dirname, '../configs/.env')
// dotenv.config({ path })

const staticPath = '/static/'
const ignorePathReg = /^\/api\/login/
const staticReg = /^\/static/

interface Request<B extends object = any> extends Koa.Request {
  // 由  koa-bodyparser 解析
  body?: B
}

export interface KoaContext<Q extends object = any, B extends object = any> extends Koa.Context {
  redis: RedisService
  wx: WxApiService
  mysql: MysqlService
  query: Q,
  request: Request<B>
  error?: {
    name: string
    stack: string
    message: string
  }
}

export class Server {
  public redisService!: RedisService
  public wxService!: WxApiService
  public mysqlService!: MysqlService
  public app: Koa = new Koa()
  constructor() {
    this.checkEnvConfig(() => {
      this.initServer()
      this.initService()
    })
  }

  private async initServer() {
    this.app.use(logger(winston))

    this.app.use(helmet())

    this.app.use(cors())

    this.app.use(bodyParser())

    this.app.use(koaStatic(join(__dirname, '..', staticPath)))

    this.app.use(async (context, next) => {
      const { host, path } = context
      if (!staticReg.test(path)) {
        context.set('Context-Type', 'application/json')
      }
      if (whiteHostSet.has(host)) {
        context.set('Access-Control-Allow-Origin', host)
      }
      if (this.checkJwtKey(context as KoaContext)) {
        return await next()
      }
      context.status = 403
      return context.body = await ResponseUtil.authError()
    })

    // this.app.use(koaJwt({
    //   secret: config.jwtSecret,
    // }).unless({ path: [/^\/login/], method: 'OPTIONS' }))

    this.app
      .use(router.routes())
      .use(router.allowedMethods())

    const server = await createServer(this.app.callback()).listen(config.port, function () {
      console.log(c.green(`⚒️ mpa-dance server listen on port ${config.port}`))
    })

    server.addListener('error', (error: Error) => {
      const { message } = error
      if (/EADDRINUSE/g.test(message)) {
        process.exit(1)
      }
    })
  }

  private checkJwtKey(ctx: KoaContext): boolean {
    const { method, path, headers } = ctx.request
    if (ignorePathReg.test(path) || staticReg.test(path)) {
      console.log(c.blue('[Jump Jwt Verify ]'))
      return true
    }
    // NOTE: 跳过 OPTIONS 请求的检查
    if (method === 'OPTIONS') {
      return true
    }
    const jwtKey = headers.authorization
    if (!jwtKey) {
      return false
    }
    try {
      const payload = jwt.verify(jwtKey, config.jwtSecret)
      const { expireAt, username } = payload as { expireAt: number, username: string }
      if (expireAt < Date.now() || username !== adminConfig.username) {
        return false
      }
      return true
    } catch (error) {
      ctx.error = error
      console.error(error)
      return false
    }
  }

  private initService() {
    this.redisService = new RedisService(this)
    this.wxService = new WxApiService(this)
    this.mysqlService = new MysqlService(this)
    this.app.context.mysql = this.mysqlService
    this.app.context.redis = this.redisService
    this.app.context.wx = this.wxService
    new GRPCService(this)
  }

  private checkEnvConfig(cb: () => void) {
    const _env = process.env
    for (const key of Object.keys(env)) {
      if (!_env[key]) {
        console.info(c.red(`[请配置环境变量${key}].......!!!!!!!`))
        return
      }
    }
    cb()
  }
}

export const server = new Server()
